package com.naiterui.ehp.bp.utils.payment;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ParameterizedTypeReference;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.OrderSnType;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.utils.beans.BeanUtil;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.net.RestHttpUtil;
import com.naiterui.ehp.bp.utils.payment.bo.CloseOrderResultBO;
import com.naiterui.ehp.bp.utils.payment.bo.RefundRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderResultBO;

import lombok.extern.slf4j.Slf4j;

/**
 * 支付功能相关HTTP接口：包括统一下单，查询订单，关闭订单，退款
 *
 * @author 王泽浩
 * @since 1.0.0
 */
@Slf4j
public class OrderUtils {

    /**
     * redis key 前缀
     */
    public static final String RDS_KEY_PRE_MALL = CommonConstant.REDIS_PRE_B2C + "mall|";

    /**
     * redis 自增 key
     */
    private static final String ORDER_SEQUENCE = RDS_KEY_PRE_MALL + "order_sequence";

    private static final int ORDER_SEQUENCE_KEY_EXPIRE = 60 * 60 * 48;

    private static final int SEQ_MAX_LEN = 3;

    /**
     * 支付域名
     */
    private static final String PAYMENT_URL = ConfigUtil.getString("payment.url");

    private OrderUtils() {
    }

    public static String getOrderSn(Integer orderType) throws BusinessException {
        switch (orderType) {
            case OrderSnType.B2C_ORDERSN:
                return genB2cOrderSn(orderType);
            default:
                return generateTradeSn(orderType);
        }

    }

    /**
     * 获取B2C订单
     */
    private static String genB2cOrderSn(Integer orderType) {

        StringBuilder orderSn = new StringBuilder();

        // 时间戳(12位)
        String dateTimeString = DateUtil.formatDate(new Date(), "yyMMddHHmmss");
        String dateString = DateUtil.formatDate(new Date(), "yyyyMMdd");

        orderSn.append(dateTimeString);

        // 订单类型(2位)
        orderSn.append(String.format("%02d", orderType));

        String rdsKey = ORDER_SEQUENCE + dateString;
        Long incr = RedisUtil.keyOps().incr(rdsKey);
        RedisUtil.keyOps().expire(rdsKey, ORDER_SEQUENCE_KEY_EXPIRE);

        String seq = String.format("%03d", incr);
        if (seq.length() > SEQ_MAX_LEN) {
            seq = seq.substring(seq.length() - SEQ_MAX_LEN);
        }
        orderSn.append(seq);
        log.debug("生成新的订单号，订单号类型:{} 订单号:{}", orderType, orderSn);
        return orderSn.toString();
    }

    /**
     * 获取订单号
     *
     * @param orderType 订单类型
     *
     * @return 该类型对应的订单号
     */
    private static String generateTradeSn(Integer orderType) throws BusinessException {
        Map<String, String> executeData = RestHttpUtil.get()
                                                      .url(PAYMENT_URL + "/order/sn/" + orderType)
                                                      .executeData(new ParameterizedTypeReference<ResponseVO<Map<String, String>>>() {
                                                      });

        return executeData.get("orderSn");
    }

    /**
     * <pre>
     * 统一下单接口
     * 在发起支付前，需要调用统一下单接口，获取"预支付交易会话标识"
     * 如果此接口调用成功，那么将会将预支付信息存入MySQL，并且记录预支付生成的渠道,期间如果有人在调用，那么直接返回预支付信息，
     * 如果预支付信息过期，那么重新调用统一下单接口，并且将之前的预支付信息设置失效,
     * 如果三方接口不能使用的话，那么系统会根据内置的支付策略来切换支付渠道，这里必须保证order_pre_payment_info
     * 表中只能有一个可以使用的预支付信息，不能存在多个渠道可以使用的预支付信息，因为如果支付渠道是多个的话，无法确定此订单使用哪个渠道支付，后面的查询订单和退款功能将会出现使用问题
     * </pre>
     *
     * @param request 请求对象，注意一些参数如notifyUrl等不用设置，方法内会自动从配置对象中获取到（前提是对应配置中已经设置）
     *
     * @return 返回预支付信息
     *
     * @throws BusinessException 支付异常
     * @Date 2017年09月09日
     * @since 1.0.0
     */
    public static UnifiedOrderResultBO pay(UnifiedOrderRequestBO request) throws BusinessException {
        return RestHttpUtil.post()
                           .url(PAYMENT_URL + "/order/pay")
                           .paramMap(BeanUtil.describe(request))
                           .executeData(new ParameterizedTypeReference<ResponseVO<UnifiedOrderResultBO>>() {
                           });
    }

    public static CloseOrderResultBO close(String orderSn) throws BusinessException {
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("orderSn", orderSn);
        return RestHttpUtil.post()
                           .url(PAYMENT_URL + "/order/close")
                           .paramMap(paramMap)
                           .executeData(new ParameterizedTypeReference<ResponseVO<CloseOrderResultBO>>() {
                           });
    }

    /**
     * <pre>
     * 查询订单
     * 该接口提供所有微信支付订单的查询，商户可以通过查询订单接口主动查询订单状态，完成下一步的业务逻辑。
     * 需要调用查询接口的情况：
     * ◆ 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知；
     * ◆ 调用支付接口后，返回系统错误或未知交易状态情况；
     * ◆ 调用被扫支付API，返回USERPAYING的状态；
     * ◆ 调用关单或撤销接口API之前，需确认支付状态；
     * </pre>
     *
     * @param orderSn 商户系统内部的订单号 不能为空
     *
     * @return 支付状态
     *
     * @throws BusinessException 支付异常
     * @Date 2017年09月09日
     * @since 1.0.0
     */
    public static Integer query(String orderSn) throws BusinessException {
        Map<String, String> request = Maps.newHashMap();
        request.put("orderSn", orderSn);
        Map<String, String> executeData = RestHttpUtil.post()
                                                      .url(PAYMENT_URL + "/order/query")
                                                      .paramMap(request)
                                                      .executeData(new ParameterizedTypeReference<ResponseVO<Map<String, String>>>() {
                                                      });
        return Integer.valueOf(executeData.get("status"));
    }

    /**
     * 退款接口
     *
     * @param request 退款信息
     *
     * @throws BusinessException 异常信息
     */
    public static void refund(RefundRequestBO request) throws BusinessException {
        RestHttpUtil.post()
                    .url(PAYMENT_URL + "/order/refund")
                    .paramMap(BeanUtil.describe(request))
                    .executeData();
    }

    /**
     * <pre>
     *     生成业务单号，规则如下：
     *      退货 ： rt+8位年月日+4位自增id
     *      换货 ： ex+8位年月日+4位自增id
     *      补货 ： ad+8位年月日+4位自增id
     *      退款 ： rf+8位年月日+4位自增id
     *  例子：10月9号的第一张退货单号为：rt201910090001
     * </pre>
     */
    public static String getBusinessSn(String prdfix) {
        Preconditions.checkArgument(StringUtils.isNoneBlank(prdfix), "前缀不能为null");
        String key = prdfix.toUpperCase() + DateUtil.formatDate(new Date(), DateUtil.DAY_TIME_FORMAT);
        Long serialNumber = RedisUtil.keyOps().incr(key);
        RedisUtil.keyOps().expire(key, 60 * 60 * 24);
        return key + String.format("%04d", serialNumber);
    }

}
